package api

import (
	"os"
	"time"
	"runtime"
	"strings"
	"math/big"
	"path/filepath"
	"kumachan/interpreter/runtime/stdlib"
	"kumachan/standalone/rx"
	"kumachan/standalone/util"
	"kumachan/interpreter/runtime/rt"
	"net/url"
)


type Locale struct {
	Language     string
	TimeZone     *time.Location
	Alternative  rt.EnumValue
}
func GetSystemLanguage() string {
	if runtime.GOOS == "windows" {
		// TODO: get system language from Windows API
		return "C"
	} else {
		var strip_encoding = func(value string) string {
			var t = strings.Split(value, ".")
			if len(t) > 0 {
				return t[0]
			} else {
				return "C"
			}
		}
		lc_all, exists := os.LookupEnv("LC_ALL")
		if exists { return strip_encoding(lc_all) }
		lang, exists := os.LookupEnv("LANG")
		if exists { return strip_encoding(lang) }
		return "C"
	}
}

func __SysUTC() rt.Value {
	return time.UTC
}
func __SysLocalTime() rt.Value {
	return time.Local
}

func convertEnvVars(raw (map[string] string)) rt.Map {
	var m = rt.NewStringMap()
	for name, value := range raw {
		m = m.Inserted(name, value)
	}
	return m
}

var cwd = (func() stdlib.Path {
	var wd, err = os.Getwd()
	if err != nil { panic("unable to get current working directory") }
	return stdlib.ParsePath(wd)
})()

func __SysPlatformInfo() rt.Value {
	return rt.Tuple(runtime.GOOS, runtime.GOARCH)
}
func __SysLocale() rt.Value {
	var locale = Locale {
		Language:    GetSystemLanguage(),
		TimeZone:    time.Local,
		Alternative: rt.None(),
	}
	return rt.Struct2Prod(locale)
}
func __SysCwd() rt.Value {
	return cwd
}
func __SysEntryModuleDirPath(h rt.InteropContext) rt.Value {
	var p = h.EntryModulePath()
	return stdlib.ParsePath(filepath.Dir(p))
}
func __SysEnv(h rt.InteropContext) rt.Value {
	return convertEnvVars(h.Environment().EnvVars)
}
func __SysArgs(h rt.InteropContext) rt.Value {
	return h.Environment().Args
}
func __SysStdin(h rt.InteropContext) rt.Value {
	return h.Environment().Stdin
}
func __SysStdout(h rt.InteropContext) rt.Value {
	return h.Environment().Stdout
}
func __SysStderr(h rt.InteropContext) rt.Value {
	return h.Environment().Stderr
}

func __SysPathParse(str string) stdlib.Path {
	return stdlib.ParsePath(str)
}
func __SysPathToString(path stdlib.Path) string {
	return path.String()
}
func __SysPathJoin(path stdlib.Path, raw rt.Value) stdlib.Path {
	var segments = rt.ListFrom(raw)
	return path.Join(segments.CopyAsStringSlice())
}

func __SysWalkDir(dir stdlib.Path) rx.Observable {
	return rx.WalkDir(dir.String()).Map(func(val rx.Object) rx.Object {
		var item = val.(rx.FileItem)
		return rt.Tuple(stdlib.ParsePath(item.Path), rt.Struct2Prod(item.State))
	})
}
func __SysListDir(dir stdlib.Path) rx.Observable {
	return rx.ListDir(dir.String()).Map(func(val rx.Object) rx.Object {
		var item = val.(rx.FileItem)
		return rt.Tuple(stdlib.ParsePath(item.Path), rt.Struct2Prod(item.State))
	})
}

func __SysOpenFilePersist(path string, mode *big.Int) rx.Observable {
	switch util.GetInt64Integer(mode) {
	case stdlib.FileReadOnly:
		return rx.OpenReadOnly(path)
	case stdlib.FileReadWrite:
		return rx.OpenReadWrite(path)
	case stdlib.FileReadWriteCreate:
		return rx.OpenReadWriteCreate(path, 0666)
	case stdlib.FileOverwrite:
		return rx.OpenOverwrite(path, 0666)
	case stdlib.FileAppend:
		return rx.OpenAppend(path, 0666)
	default:
		panic("invalid mode")
	}
}

func __SysFileClose(f rx.File) rx.Observable {
	return f.Close()
}
func __SysFileGetState(f rx.File) rx.Observable {
	return f.State().Map(func(state rx.Object) rx.Object {
		return rt.Struct2Prod(state)
	})
}
func __SysFileSeekStart(f rx.File) rt.FuncValue {
	return rt.ValFuncNative(func(arg rt.Value, _ rt.InteropContext) rt.Value {
		var offset = util.GetInt64Integer(arg.(*big.Int))
		return f.SeekStart(offset)
	})
}
func __SysFileSeekDelta(f rx.File) rt.FuncValue {
	return rt.ValFuncNative(func(arg rt.Value, _ rt.InteropContext) rt.Value {
		var offset = util.GetInt64Integer(arg.(*big.Int))
		return f.SeekDelta(offset)
	})
}
func __SysFileSeekEnd(f rx.File) rt.FuncValue {
	return rt.ValFuncNative(func(arg rt.Value, _ rt.InteropContext) rt.Value {
		var offset = util.GetInt64Integer(arg.(*big.Int))
		return f.SeekEnd(offset)
	})
}
func __SysFileRead(f rx.File) rt.FuncValue {
	return rt.ValFuncNative(func(arg rt.Value, _ rt.InteropContext) rt.Value {
		var amount = util.GetUintNumber(arg.(*big.Int))
		return f.Read(amount)
	})
}
func __SysFileReadAll(f rx.File) rx.Observable {
	return f.ReadAll()
}
func __SysFileReadChar(f rx.File) rx.Observable {
	return f.ReadChar()
}
func __SysFileReadLine(f rx.File) rx.Observable {
	return f.ReadLine()
}
func __SysFileReadLines(f rx.File) rx.Observable {
	return f.ReadLines()
}
func __SysFileWrite(f rx.File) rt.FuncValue {
	return rt.ValFuncNative(func(arg rt.Value, _ rt.InteropContext) rt.Value {
		var data = arg.([] byte)
		return f.Write(data)
	})
}
func __SysFileWriteString(f rx.File) rt.FuncValue {
	return rt.ValFuncNative(func(arg rt.Value, _ rt.InteropContext) rt.Value {
		var str = arg.(string)
		return f.WriteString(str)
	})
}
func __SysFileWriteLine(f rx.File) rt.FuncValue {
	return rt.ValFuncNative(func(arg rt.Value, _ rt.InteropContext) rt.Value {
		var line = arg.(string)
		return f.WriteLine(line)
	})
}

func __SysUrlParse(str string) rt.EnumValue {
	var u, err = url.Parse(str)
	if err != nil {
		return rt.None()
	} else {
		return rt.Some(u)
	}
}

func __SysHttpGet(url string) rx.Observable {
	return rx.HttpGet(url)
}
func __SysHttpResStatus(res rx.HttpResponse) *big.Int {
	return util.GetNumberUint(res.StatusCode)
}
func __SysHttpResHeader(res rx.HttpResponse) rt.Map {
	var h = res.Header
	var m = rt.NewStringMap()
	for k, v := range h {
		m = m.Inserted(k, v)
	}
	return m
}
func __SysHttpResBody(res rx.HttpResponse) ([] byte) {
	return res.Body
}

func __SysExit(code *big.Int) rx.Observable {
	return rx.NewSyncWithSender(func(_ rx.Sender) {
		// Graceful exit through `qt.Exit` is NOT used now.
		// See the [Problem] described below.
		/* qt.CommitTask(func() {
			qt.Exit(int32(code.Int64()))
		}) */
		// [Problem]
		// On Windows Server 2019 Datacenter,
		//   exception 0xc0000005 (or 0x80000003) is observed
		//   when trying to exit process while GUI is running.
		// The process will NOT terminate in that case,
		//   it hangs after showing the exception message,
		//   even if we call `os.Exit` here. (WTF!?)
		// As a workaround, in the Windows version
		//   of the `ExitProcess` function,
		//   a direct Windows API call is performed,
		//   in order to force the process to exit.
		ExitProcess(uint32(code.Int64()))
		panic("program should have exited")
	})
}


